package im.oen.boot.common.utils;

import com.fasterxml.jackson.annotation.JsonIgnore;
import im.oen.boot.common.constant.Charsets;
import im.oen.boot.common.data.StateBase;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Web客户端
 *
 * @author Kevin.Xu
 */
public class WebClient {

    private static final ConcurrentHashMap<String, List<Cookie>> COOKIE_STORE = new ConcurrentHashMap<>();
    private static OkHttpClient client;

    static {
        client = new OkHttpClient.Builder()
                .cookieJar(new CookieJar() {
                    @Override
                    public void saveFromResponse(@NotNull HttpUrl httpUrl, @NotNull List<Cookie> list) {
                        COOKIE_STORE.put(httpUrl.host(), list);
                    }

                    @Override
                    public List<Cookie> loadForRequest(HttpUrl httpUrl) {
                        List<Cookie> cookies = COOKIE_STORE.get(httpUrl.host());
                        return cookies != null ? cookies : new ArrayList<>();
                    }
                }).build();
    }

    private static HttpResponse call(Request request) throws Exception {
        SyncContainer<HttpResponse> container = new SyncContainer<>();
        client.newCall(request).enqueue(new Callback() {
            @Override
            public void onFailure(Call call, IOException e) {
                container.error(e);
            }

            @Override
            public void onResponse(Call call, Response response) {
                try {
                    HttpResponse httpResponse = new HttpResponse(response);
                    httpResponse.setRequestUrl(response.request().url().toString());
                    container.set(httpResponse);
                } catch (Exception e) {
                    container.error(e);
                }
            }
        });
        return container.safeGetOrThrow();
    }

    public static void upload(String method, String url, byte[] bytes) throws Exception {
        RequestBody requestBody = RequestBody.create(bytes, MediaType.parse("application/octet-stream"));
        Request request = new Request.Builder().url(url).method(method, requestBody).build();
        call(request);
    }

    public static void postUpload(String url, String key, byte[] bytes) throws Exception {
        RequestBody requestBody = new MultipartBody.Builder()
                .setType(MultipartBody.FORM)
                .addFormDataPart("file", key, RequestBody.create(bytes, MediaType.parse("multipart/form-data")))
                .build();
        Request request = new Request.Builder().url(url).post(requestBody).build();
        call(request);
    }

    public static byte[] download(String url) throws Exception {
        final Request request = new Request.Builder().url(url).get().build();
        HttpResponse res = call(request);
        return (res != null) ? res.getBodyBytes() : null;
    }

    public static HttpResponse request(String url, String method, Map<String, String> headers, Map<String, String> params, String json) throws Exception {
        RequestBody requestBody = null;
        if (Checker.isNotEmpty(json)) {
            requestBody = RequestBody.create(json, MediaType.parse("application/json;charset=utf-8"));
        }

        HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
        if (Checker.isNotEmpty(params)) {
            for (Map.Entry<String, String> entry : params.entrySet()) {
                urlBuilder.addQueryParameter(entry.getKey(), entry.getValue());
            }
        }
        Request.Builder request = new Request.Builder()
                .url(urlBuilder.build())
                .method(method, requestBody);

        if (Checker.isNotEmpty(headers)) {
            for (Map.Entry<String, String> entry : headers.entrySet()) {
                request.addHeader(entry.getKey(), entry.getValue());
            }
        }
        return call(request.build());
    }

    public static HttpResponse post(String url, String json) throws Exception {
        return request(url, "POST", null, null, json);
    }

    public static HttpResponse get(String url, Map<String, String> params) throws Exception {
        return request(url, "GET", null, params, null);
    }

    public static class HttpResponse extends StateBase {
        private String requestUrl;
        private int code;
        private String message;
        private Map<String, String> headers;
        private String bodyString;
        @JsonIgnore
        private byte[] bodyBytes;

        public String getBodyString() {
            if (Checker.isNotEmpty(bodyString)) {
                return bodyString;
            }

            if (Checker.isNotEmpty(bodyBytes)) {
                this.bodyString = new String(this.bodyBytes, Charsets.UTF_8);
            }
            return this.bodyString;
        }

        public byte[] getBodyBytes() {
            return bodyBytes;
        }

        public void setBodyBytes(byte[] bodyBytes) {
            this.bodyBytes = bodyBytes;
        }

        public String getRequestUrl() {
            return requestUrl;
        }

        public void setRequestUrl(String requestUrl) {
            this.requestUrl = requestUrl;
        }

        public int getCode() {
            return code;
        }

        public void setCode(int code) {
            this.code = code;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public Map<String, String> getHeaders() {
            return headers;
        }

        public void setHeaders(Map<String, String> headers) {
            this.headers = headers;
        }

        public String getHeader(String name) {
            if (Checker.isEmpty(headers)) {
                return null;
            }
            return headers.get(name);
        }

        public HttpResponse(Response response) throws IOException {
            if (Checker.isEmpty(response)) {
                return;
            }
            this.setBodyBytes(response.body().bytes());
            this.setCode(response.code());
            this.setMessage(response.message());

            Headers hds = response.headers();
            if (Checker.isEmpty(hds)) {
                return;
            }
            Set<String> headerNames = hds.names();
            Map<String, String> headers = new HashMap<>();
            for (String headerName : headerNames) {
                headers.put(headerName, hds.get(headerName));
            }
            this.setHeaders(headers);
        }

        @Override
        public boolean isSuccess() {
            return this.code >= 200 && this.code < 300;
        }
    }
}
